// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

#import "RIBRotate.h"

@implementation RIBRotate

+ initialize { return [RIBRotate setVersion:1], self; }

- updateMatrix
{
  double  theta;
  double   alphaHat, versineOfAlphaHat;
  RtPoint  aHat;
  RtFloat  divisor;


  N3D_CopyMatrix(N3DIdentityMatrix, myMatrix);
  theta = toRadians(angle);
  alphaHat = theta;

  // three simple cases are rotation about x, y, or z
  if ((dx == 1.0) && (dy == 0.0) && (dz == 0.0))  // X
  {  myMatrix[1][1] = (RtFloat)cos(theta); 
     myMatrix[1][2] = (RtFloat)sin(theta); 
     myMatrix[2][1] = -1. * (RtFloat)sin(theta); 
     myMatrix[2][2] = (RtFloat)cos(theta); 
     return self;
  }
  if ((dx == 0.0) && (dy == 1.0) && (dz == 0.0))  // Y
  {  myMatrix[0][0] = (RtFloat)cos(theta); 
     myMatrix[0][2] = -1. * (RtFloat)sin(theta); 
     myMatrix[2][0] = (RtFloat)sin(theta); 
     myMatrix[2][2] = (RtFloat)cos(theta); 
     return self;
  }
  if ((dx == 0.0) && (dy == 0.0) && (dz == 1.0))  // Z
  {  myMatrix[0][0] = (RtFloat)cos(theta); 
     myMatrix[0][1] = (RtFloat)sin(theta); 
     myMatrix[1][0] = -1. * (RtFloat)sin(theta); 
     myMatrix[1][1] = (RtFloat)cos(theta); 
     return self;
  }

  // yuck.  we now have to do rotation about an arbitrary axis.  oh joy.

  divisor = (RtFloat)(sqrt((dx * dx) + (dy * dy) + (dz * dz)));
  aHat[0] = dx/divisor;
  aHat[1] = dy/divisor;
  aHat[2] = dz/divisor;
  versineOfAlphaHat = 1 - cos(alphaHat);

  myMatrix[0][0] = 1 + (((aHat[0] * aHat[0]) - 1) * versineOfAlphaHat);
  myMatrix[0][1] = (aHat[2] * sin(alphaHat)) + (aHat[0] * aHat[1] * versineOfAlphaHat);
  myMatrix[0][2] = (-1 * aHat[1] * sin(alphaHat)) + (aHat[2] * aHat[0] * versineOfAlphaHat);
  myMatrix[1][0] = (-1 * aHat[2] * sin(alphaHat)) + (aHat[0] * aHat[1] * versineOfAlphaHat);
  myMatrix[1][1] = 1 + (((aHat[1] * aHat[1]) - 1) * versineOfAlphaHat);
  myMatrix[1][2] = (aHat[0] * sin(alphaHat)) + (aHat[1] * aHat[0] * versineOfAlphaHat);
  myMatrix[2][0] = (aHat[1] * sin(alphaHat)) + (aHat[2] * aHat[0] * versineOfAlphaHat);
  myMatrix[2][1] = (-1 * aHat[0] * sin(alphaHat)) + (aHat[1] * aHat[0] * versineOfAlphaHat);
  myMatrix[2][2] = 1 + (((aHat[2] * aHat[2]) - 1) * versineOfAlphaHat);

  //NXLogError("RIBRotate (-updateMatrix):\n");
  //NXLogError("\t%f %f %f %f\n",  myMatrix[0][0], myMatrix[0][1], myMatrix[0][2], myMatrix[0][3]);
  //NXLogError("\t%f %f %f %f\n",  myMatrix[1][0], myMatrix[1][1], myMatrix[1][2], myMatrix[1][3]);
  //NXLogError("\t%f %f %f %f\n",  myMatrix[2][0], myMatrix[2][1], myMatrix[2][2], myMatrix[2][3]);
  //NXLogError("\t%f %f %f %f\n", myMatrix[3][0], myMatrix[3][1], myMatrix[3][2], myMatrix[3][3]);

  return self;
}

- init
{
  [super init];
  angle = 90.0;
  dx = 1.0;
  dy = 0.0;
  dz = 0.0;

  [self updateMatrix];

  return self;
}

- awake
{
  [super awake];
  [self updateMatrix];

  return self;
}

- setAngle:(RtFloat)newAngle dx:(RtFloat)newDX dy:(RtFloat)newDY dz:(RtFloat)newDZ
{
  angle = newAngle;
  dx = newDX;
  dy = newDY;
  dz = newDZ;
  [self updateMatrix];

  return self;
}

- (BOOL)isLerpable { return YES; }

// note: because we've made the WWSampleList "safe" for having
// multiple samples with the same data, it's perfectly valid to return
// yourself or b
- lerpWith:b by:(float)uValue
{
   id      newMe = nil;
   RtFloat  angleA, dxA, dyA, dzA;
   RtFloat  angleB, dxB, dyB, dzB;

    if (([self class] != [b class]) || (uValue <= 0.0))
    {  return self;
    }

    if (uValue >= 1.0)
    {  return b;
    }

    newMe = [super lerpWith:b by:uValue]; // this makes a copy for us
 
    // okay, now do the specific stuff for this class
    [self getAngle:&angleA dx:&dxA dy:&dyA dz:&dzA];
    [b getAngle:&angleB dx:&dxB dy:&dyB dz:&dzB];
    [newMe setAngle:(angleA + ((angleB - angleA) * uValue))
              dx:(dxA + ((dxB - dxA) * uValue))
              dy:(dyA + ((dyB - dyA) * uValue))
              dz:(dzA + ((dzB - dzA) * uValue))];

    return newMe;
}


- lerpSelfWith:b by:(float)uValue
{
  RtFloat  angleA, dxA, dyA, dzA;
  RtFloat  angleB, dxB, dyB, dzB;

   if (([self class] != [b class]) || (uValue <= 0.0))
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }

   [super lerpSelfWith:b by:uValue]; // this makes a copy for us

   // okay, now do the specific stuff for this class
   [self getAngle:&angleA dx:&dxA dy:&dyA dz:&dzA];
   [b getAngle:&angleB dx:&dxB dy:&dyB dz:&dzB];
   [self setAngle:(angleA + ((angleB - angleA) * uValue))
             dx:(dxA + ((dxB - dxA) * uValue))
             dy:(dyA + ((dyB - dyA) * uValue))
             dz:(dzA + ((dzB - dzA) * uValue))];

   return self;
}


- (BOOL)isMoot
{
  if (!angle)  {  return YES; }
  return NO;
}

- (BOOL)theSameAs:otherRIBCommand
{
  RtFloat  theAngle, theDX, theDY, theDZ;


  [otherRIBCommand getAngle:&theAngle dx:&theDX dy:&theDY dz:&theDZ];
  if (angle != theAngle)
  {  return NO;
  }
  if (dx != theDX)
  {  return NO;
  }
  if (dy != theDY)
  {  return NO;
  }
  if (dz != theDZ)
  {  return NO;
  }

  return [super theSameAs:otherRIBCommand];
}

- setDX:(RtFloat)newDX dy:(RtFloat)newDY dz:(RtFloat)newDZ
{
  dx = newDX;
  dy = newDY;
  dz = newDZ;

  N3D_CopyMatrix(N3DIdentityMatrix, myMatrix);
  myMatrix[3][0] = dx;
  myMatrix[3][1] = dy;
  myMatrix[3][2] = dz;

  return self;
}

- getAngle:(RtFloat *)anglePtr dx:(RtFloat *)dxPtr dy:(RtFloat *)dyPtr dz:(RtFloat *)dzPtr
{
  *anglePtr = angle;
  *dxPtr = dx;
  *dyPtr = dy;
  *dzPtr = dz;

  return self;
}

- renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime 
{
  RiRotate(angle, dx, dy, dz);

  return self;
}

- transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{ 
  RtMatrix  aMatrix, tmpMatrix;


  [attributeState getTransformMatrix:aMatrix];
  N3DMultiplyMatrix(myMatrix, aMatrix, tmpMatrix);
  [attributeState setTransformMatrix:tmpMatrix];

  //NXLogError("RIBRotate myMatrix (-transformCTM:startingAt:endingAt):\n");
  //NXLogError("\t%f %f %f %f\n",  myMatrix[0][0], myMatrix[0][1], myMatrix[0][2], myMatrix[0][3]);
  //NXLogError("\t%f %f %f %f\n",  myMatrix[1][0], myMatrix[1][1], myMatrix[1][2], myMatrix[1][3]);
  //NXLogError("\t%f %f %f %f\n",  myMatrix[2][0], myMatrix[2][1], myMatrix[2][2], myMatrix[2][3]);
  //NXLogError("\t%f %f %f %f\n", myMatrix[3][0], myMatrix[3][1], myMatrix[3][2], myMatrix[3][3]);
  //NXLogError("RIBRotate aMatrix (-transformCTM:startingAt:endingAt):\n");
  //NXLogError("\t%f %f %f %f\n",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
  //NXLogError("\t%f %f %f %f\n",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
  //NXLogError("\t%f %f %f %f\n",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
  //NXLogError("\t%f %f %f %f\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);

  return self; 
}

- (BOOL)isMotionBlurrable { return YES; }

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "Rotate %f %f %f %f;", angle, dx, dy, dz); 
   return self;
}

- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "Rotation {\n");
   for (i = 0; i < (tab+1); i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "rotation %f %f %f   %f\n", dx, dy, dz, angle);
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "}");

   return self;

}
#define typeVector "ffff"
#define typeValues &angle, &dx, &dy, &dz

- read:(NXTypedStream*)stream 
{
    int version;
    [super read:stream];

NX_DURING
    version = NXTypedStreamClassVersion(stream,"RIBRotate");
    if (version == 0) NXReadTypes(stream,"i",&version), version=1;
    if (version == 1)
    {  NXReadTypes(stream,typeVector,typeValues);
    } 
NX_HANDLER
   NXLogError("in read: %s, exception [%d] raised.\n", 
                 [[self class] name], NXLocalHandler.code);
   return nil;
NX_ENDHANDLER
    return self;
}

- write:(NXTypedStream*)stream 
{
    [super write:stream];

    NXWriteTypes(stream,typeVector, typeValues);

    return self;
}

@end
